from copy import copy
from typing import Optional

from api import *
from base import join_path
from config import pimeConfig, Config
from im_editor import Editor
from im_ejector import Ejector
from im_key_event import *
from im_translator import Translators
from pime_setting import startup


@dataclass
class HotKey(object):
    when: str = 'always'
    key: ImKeyEvent = ImKeyEvent()
    action: str = ''


class PimeEngine(ImEngine):
    schemaConfig = {}
    activeSchema = -1

    def __init__(self) -> None:
        super().__init__()
        print('>>> create PimeEngine.')
        self.ims: Optional[ImService] = None
        self.config = pimeConfig
        self.cs: CandidateState = CandidateState()
        self.editor: Optional[ImEditor] = None
        self.translator: Optional[ImTranslator] = None
        self.ejector: Optional[ImEjector] = None
        self.modes = {}
        self.asciiMode = False
        self.selectKeys = '1234567890'
        self.keyMap = {}  # ImKeyEvent -> commands
        self.punctuate = {}  # 标点符号映射
        self.keyUpCommand = None

    def useImService(self, im_service: ImService):
        self.ims = im_service
        self.applyConfig()

    def stop(self):
        self.cs.reset()

    def applyConfig(self):
        index = self.config.get('defaultSchema')
        if index == PimeEngine.activeSchema:
            pass
        else:
            f = pimeConfig.get('schemas')[index]
            PimeEngine.schemaConfig = Config.loadFile(join_path(self.ims.getWorkDir(), f))
            PimeEngine.activeSchema = index
        config = PimeEngine.schemaConfig
        self.editor = Editor(self.cs, config.get('editor'))
        self.translator = Translators.create(config.get('translator'))
        self.ejector = Ejector(config.get('ejector'))
        modes = config.get('modes')
        for k, v in config.get('defaultModes').items():
            self.modes[k] = modes.get(k)[v]
        print('>>> applyConfig, self.modes={}'.format(self.modes))
        if self.config.get('selectKeys') is not None:
            self.selectKeys = self.config.get('selectKeys')
        self.keyMap['always'] = {}
        self.keyMap['composing'] = {}
        self.keyMap['paging'] = {}
        for item in self.config.get('keyMap', []):
            when = item.get('when')
            key = item.get('key')
            action = item.get('action')
            keyEvent = ImKeyEvent()
            keyEvent.apply(seq=key)
            if when in self.keyMap:
                self.keyMap[when][keyEvent.value] = action
            else:
                print('Invalid keyMap item:{}'.format(item))
        if self.config.get('punctuate') is not None:
            for item in self.config.get('punctuate'):
                x = item[0]
                y = item[2:]
                self.punctuate[x] = y

    def onActivate(self):
        # self.showTooltip('onActivate()')
        self.ims.applyTheme(self.config.get('candidateBar'))

    def onDeactivate(self):
        self.config = None
        self.editor = None
        self.translator = None
        self.ejector = None

    def isComposing(self) -> bool:
        return self.cs.hasComposition()

    def isPaging(self) -> bool:
        return self.cs.hasCandidate() and self.translator.getStart() > 0

    def getMode(self, name: str):
        return self.modes.get(name)

    def getModes(self) -> dict:
        return self.modes

    def testKeyDown(self, key_event: ImKeyEvent) -> bool:
        print('>>> <{}> testKeyDown({})'.format(__name__, key_event))
        # check if it is a hot key or composing code
        self.keyUpCommand = None
        isHotKey = False
        isComposingCode = False
        always = self.keyMap.get('always')
        if key_event.value in always:
            isHotKey = True
        if not isHotKey:
            isComposingCode = key_event.isVisibleCharOnly()
            if not isComposingCode and self.isComposing():
                print('>>> <{}> composing'.format(__name__))
                isHotKey = key_event.value in self.keyMap.get('composing')
                isComposingCode = key_event.isVisibleCharOnly() or key_event.value == VK_IM_BACKSPACE
            elif not isComposingCode and self.isPaging():
                print('>>> <{}> paging'.format(__name__))
                isHotKey = key_event.value in self.keyMap.get('paging')
                isComposingCode = key_event.isVisibleCharOnly() or key_event.value == VK_IM_BACKSPACE
        print('>>> <{}> testKeyDown(), isHotKey={}, isComposingCode={}'.format(__name__, isHotKey, isComposingCode))
        return isHotKey or isComposingCode

    def onKeyDown(self, key_event: ImKeyEvent):
        print('>>> <{}> onKeyDown({}), isComposing={}, isPaging={}'.format(__name__, key_event, self.isComposing(),
                                                                           self.isPaging()))
        always = self.keyMap.get('always')
        handled = False
        value = key_event.value
        ch = key_event.getChar()
        command = None
        if value in always:
            command = always.get(value)
            handled = True
        if handled:
            pass
        elif self.asciiMode:
            self.commit(self.cs.composition + ch)
            self.cs.reset()
            handled = True
        elif self.isComposing():
            if value in self.keyMap.get('composing'):
                command = self.keyMap.get('composing').get(value)
                handled = True
            elif value == VK_IM_BACKSPACE:
                print('>>> <{}> backspace'.format(__name__))
                self.editor.backspace()
                self.cs.candidateList = self.translator.translate(self.cs.composition)
                handled = True
            elif key_event.isVisibleCharOnly():
                oldState = copy(self.cs)
                if self.editor.accept(ch):
                    self.cs.candidateList = self.translator.translate(self.cs.composition)
                    if self.ejector.eject(oldState, self.cs, self.ims):  # 上屏后再次查询剩余的编码
                        print('>>> onKeyDown, ejected translate again.')
                        self.cs.candidateList = self.translator.translate(self.cs.composition)
                    handled = True
                elif self.cs.hasCandidate() and key_event.isVisibleCharOnly() and ch in self.selectKeys:
                    index = self.selectKeys.index(ch)
                    print('>>> commitByIndex, index={}'.format(index))
                    command = 'commitByIndex:' + str(index)
                    handled = True
                else:  # punctuate?
                    print('>>> <{}> onKeyDown, ignored 11'.format(__name__))
        elif self.isPaging():
            if value in self.keyMap.get('paging'):
                command = self.keyMap.get('paging').get(value)
                handled = True
        elif key_event.isVisibleCharOnly():
            if self.editor.accept(ch):
                self.cs.candidateList = self.translator.translate(self.cs.composition)
                handled = True
            elif ch in self.punctuate:
                self.commit(self.punctuate.get(ch))
                self.cs.reset()
                handled = True
            else:
                self.commit(ch)
                self.cs.reset()
                handled = True
        else:
            print('>>> <{}> onKeyDown, ignored 33'.format(__name__))
        print('>>> <{}> onKeyDown, handled={}, command={}'.format(__name__, handled, command))
        if command is not None:
            self.keyUpCommand = command  # 在keyUp时处理更好
        if handled:
            self.updateComposition()
            self.updateCandidate()
        return handled

    def onKeyUp(self, key_event: ImKeyEvent):
        print('>>> <{}> onKeyUp({})'.format(__name__, key_event))
        if self.keyUpCommand is not None:
            self.execute(self.keyUpCommand)
            self.keyUpCommand = None
        # elif key_event.modifier > 0:
        #   self.showTooltip(str(key_event)) # tim.exe 的聊天窗口，tooltip 提示会导致已选择的文本选失去选中状态
        return key_event.value != 0

    def execute(self, command: str, args=None):
        print('>>> <{}> execute({})'.format(__name__, command))
        t = self.translator
        if command == 'toggleAsciiMode':
            self.asciiMode = not self.asciiMode
            self.showTooltip('En' if self.asciiMode else '中')
        elif command == 'clearInput':
            self.cs.reset()
            self.updateComposition()
            self.updateCandidate()
        elif command == 'commitInput':
            self.commit(self.cs.composition)
            self.cs.reset()
            self.updateComposition()
            self.updateCandidate()
        elif command == 'prevPage':
            self.cs.candidateList = t.translate(self.cs.composition, t.pageUp())
            self.updateCandidate()
        elif command == 'nextPage':
            self.cs.candidateList = t.translate(self.cs.composition, t.pageDown())
            self.updateCandidate()
        elif command == 'moveToPrev':
            self.cs.moveToPrev()
            self.updateCandidate()
        elif command == 'moveToNext':
            self.cs.moveToNext()
            self.updateCandidate()
        elif command == 'commitSelected' and self.cs.hasCandidate():
            text = self.cs.candidateTextAt(self.cs.candidateCursor)
            self.commit(text)
            self.cs.reset()
            self.updateCandidate()
        elif command.startswith('commitByIndex:') and self.cs.hasCandidate():
            index = int(command[14:])
            if 0 <= index < self.cs.count():
                text = self.cs.candidateTextAt(index)
                self.commit(text)
                self.cs.reset()
                self.updateCandidate()

    def _handleWhenComposing(self, key_event):
        if not self.isComposing():
            return False

    def _handleWhenPaging(self, key_event):
        if not self.isPaging():
            return False

    def updateComposition(self):
        self.ims.setComposition(self.cs.composition)

    def updateCandidate(self):
        if self.cs.hasCandidate():
            self.ims.showCandidates(self.cs)
        else:
            self.ims.hideCandidates()

    def commit(self, text=''):
        self.ims.commit(text)
        if text == 'set':
            print('>>> start PimeSetting')
            startup()

    def showTooltip(self, msg, timeout=1):
        self.ims.showTooltip(msg)
